/*
 * ZooInspector
 * 
 * Copyright 2010 Colin Goodheart-Smithe

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package org.apache.zookeeper.inspector.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.SwingWorker;
import javax.swing.event.TreeSelectionListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.inspector.manager.NodeListener;
import org.apache.zookeeper.inspector.manager.ZooInspectorManager;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;

import com.nitido.utils.toaster.Toaster;


/**
 * @author CGSmithe
 * 
 */
public class ZooInspectorTreeViewer extends JPanel implements NodeListener
{
	/**
	 * 
	 */
	private static final long serialVersionUID = -1896511020793714926L;
	private final ZooInspectorManager zooInspectorManager;
	private final JTree tree;
	private final Toaster toasterManager;
	private ZooInspectorPanel zooInspectorPanel;
	
	public JTree getTree() {
		return tree;
	}
	
	public void setZooInspectorPanel(ZooInspectorPanel zooInspectorPanel) {
		this.zooInspectorPanel = zooInspectorPanel;
	}
	
	public  static String getSavePath() {
		JFileChooser fc = new JFileChooser();
		fc.setDialogTitle("保存地址");
		fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
		fc.setFileFilter(new FileFilter() {
			
			@Override
			public String getDescription() {
				return "数据文件(*.properties)";
			}
			
			@Override
			public boolean accept(File f) {
				if(f.isDirectory()) {
					return true;
				}
				String name = f.getName();
				if(name.endsWith(".properties")) {
					return true;
				}
				return false;
			}
		});
		int num = fc.showDialog(null, "确定");
		if (num == 0) {
			String filePath = fc.getSelectedFile().getPath();
			if(filePath.endsWith(".properties")) {
				return filePath;
			}
			return filePath+".properties";
		}

		return "";
	}
	
	public static String getFilePath() {
		JFileChooser fc = new JFileChooser();
		fc.setDialogTitle("选择文件");
		fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
		fc.setFileFilter(new FileFilter() {
			
			@Override
			public String getDescription() {
				return "数据文件(*.properties)";
			}
			
			@Override
			public boolean accept(File f) {
				if(f.isDirectory()) {
					return true;
				}
				String name = f.getName();
				if(name.endsWith(".properties")) {
					return true;
				}
				return false;
			}
		});
		int num = fc.showDialog(null, "选择");
		if (num == 0) {
			return fc.getSelectedFile().getPath();
		}
		return "";
	}

	/**
	 * @param zooInspectorManager
	 * @param listener
	 */
	public ZooInspectorTreeViewer(final ZooInspectorManager zooInspectorManager,
			TreeSelectionListener listener)
	{
		this.zooInspectorManager = zooInspectorManager;
		this.setLayout(new BorderLayout());
		final JPopupMenu popupMenu = new JPopupMenu();
		final JMenuItem addNotify = new JMenuItem("增加节点监听");
		this.toasterManager = new Toaster();
		this.toasterManager.setBorderColor(Color.BLACK);
		this.toasterManager.setMessageColor(Color.BLACK);
		this.toasterManager.setToasterColor(Color.WHITE);
		addNotify.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				List<String> selectedNodes = getSelectedNodes();
				zooInspectorManager.addWatchers(selectedNodes, ZooInspectorTreeViewer.this);
			}
		});
		final JMenuItem removeNotify = new JMenuItem("移除节点监听");
		removeNotify.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				List<String> selectedNodes = getSelectedNodes();
				zooInspectorManager.removeWatchers(selectedNodes);
			}
		});
		final JMenuItem dataLeadingOut = new JMenuItem("节点数据导出");
		dataLeadingOut.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				List<String> selectedNodes = getSelectedNodes();
				String path = getSavePath();
				try {
					if(path == null || path.trim().isEmpty())
						return;
					int showConfirmDialog = JOptionPane.showConfirmDialog(zooInspectorPanel, "是否导出父节点?", "导出", JOptionPane.YES_NO_OPTION);
					boolean bool = false;
					if(showConfirmDialog==0) {
						bool = true;
					}
					if(showConfirmDialog == -1) {
						JOptionPane.showConfirmDialog(zooInspectorPanel, "导出任务已取消!!!","导出",-1,JOptionPane.WARNING_MESSAGE);
						return;
					}
					boolean leadOut = zooInspectorManager.leadOut(selectedNodes, path , bool);
					if(leadOut) {
						
						JOptionPane.showConfirmDialog(zooInspectorPanel, "数据导出完成","节点数据导出",JOptionPane.PLAIN_MESSAGE);
					} else {
						JOptionPane.showConfirmDialog(zooInspectorPanel, "没有权限导出数据","节点数据导出",JOptionPane.PLAIN_MESSAGE);

					}
				} catch (Exception e1) {
					JOptionPane.showConfirmDialog(zooInspectorPanel, "数据导出失败\n"+e1.getMessage(),"节点数据导出",JOptionPane.ERROR_MESSAGE);
					e1.printStackTrace();
				}
			}
		});
		final JMenuItem dataLeadingIn = new JMenuItem("节点数据导入");
		dataLeadingIn.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				List<String> selectedNodes = getSelectedNodes();
				String path = getFilePath();
				try {
					if(path == null || path.trim().isEmpty() || !new File(path).exists()) {
						return;
					}
					zooInspectorManager.leadIn(selectedNodes, path);
					JOptionPane.showConfirmDialog(zooInspectorPanel, "数据导入完成","节点数据导入",JOptionPane.PLAIN_MESSAGE);
				} catch (Exception e1) {
					JOptionPane.showConfirmDialog(zooInspectorPanel, "数据导入失败\n"+e1.getMessage(),"节点数据导入",JOptionPane.ERROR_MESSAGE);
					e1.printStackTrace();
				}
			}
		});
		final JMenuItem addAcl = new JMenuItem("增加节点权限");
		addAcl.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				List<String> selectedNodes = getSelectedNodes();
				String aclString = JOptionPane.showInputDialog(zooInspectorPanel,
						"请输入 ACL(READ[R|r]、WRITE[W]、CREATE[C]、DELETE[D]、ADMIN[AD]、ALL[A]) \n 格式例如:  username1:password1/R,..;username2:password2/R,..", "节点权限增加",
						JOptionPane.INFORMATION_MESSAGE);
				List<ACL> list = new ArrayList<ACL>();
				if(aclString!=null&&!aclString.trim().isEmpty()) {
					String[] args = aclString.trim().split(";");
					for(String aclStr : args) {
						if(aclStr.matches(".+/.+")) {
							String[] as = aclStr.trim().split("/");
							String keys = as[0].trim();
							if(!keys.matches(".+:.+")) {
								continue;
							}
							String[] up = keys.split(":");
							String username = up[0].trim();
							String password = up[1].trim();
							try {
								keys = DigestAuthenticationProvider.generateDigest(username+":"+password);
							} catch (NoSuchAlgorithmException e1) {
							}
							String pString = as[1];
							String[] ps = pString.trim().split(",");
							for(String string : ps) {
								string = string.trim().toUpperCase();
								int perms = Integer.MIN_VALUE;
								if("R".equals(string)||"READ".equals(string)) {
									perms = ZooDefs.Perms.READ;
								}
								if("W".equals(string)||"WRITE".equals(string)) {
									perms = ZooDefs.Perms.WRITE;
								}
								if("C".equals(string)||"CREATE".equals(string)) {
									perms = ZooDefs.Perms.CREATE;
								}
								if("D".equals(string)||"DELETE".equals(string)) {
									perms = ZooDefs.Perms.DELETE;
								}
								if("AD".equals(string)||"ADMIN".equals(string)) {
									perms = ZooDefs.Perms.ADMIN;
								}
								if("A".equals(string)||"ALL".equals(string)) {
									perms = ZooDefs.Perms.ALL;
								}
								if(perms!=Integer.MIN_VALUE) {
									list.add(new ACL(perms, new Id("digest", keys)));
								}
							}
						}
					}
					try {
						
						zooInspectorManager.addAcl(selectedNodes, list);
					} catch (Exception e1) {
						JOptionPane.showConfirmDialog(zooInspectorPanel, "没有权限增加节点权限","节点权限增加",JOptionPane.ERROR_MESSAGE);
					}
				}
			}
		});
		final JMenuItem delAcl = new JMenuItem("删除节点权限");
		delAcl.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				List<String> selectedNodes = getSelectedNodes();
				int num = JOptionPane.showConfirmDialog(zooInspectorPanel, "是否删除该节点权限?","节点权限删除",JOptionPane.ERROR_MESSAGE);
				if(num == 0) {
					try{
						
						zooInspectorManager.alterAcl(selectedNodes, null);
					} catch (Exception e1) {
						JOptionPane.showConfirmDialog(zooInspectorPanel, "没有权限删除节点权限","节点权限删除",JOptionPane.ERROR_MESSAGE);
					}
				}
			}
		});
		final JMenuItem alterAcl = new JMenuItem("修改节点权限");
		alterAcl.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				List<String> selectedNodes = getSelectedNodes();
				String aclString = JOptionPane.showInputDialog(zooInspectorPanel,
						"请输入 ACL(READ[R|r]、WRITE[W]、CREATE[C]、DELETE[D]、ADMIN[AD]、ALL[A]) \n 格式例如: username1:password1/R,..;username2:password2/R,..", "节点权限修改",
						JOptionPane.INFORMATION_MESSAGE);
				List<ACL> list = new ArrayList<ACL>();
				if(aclString!=null&&!aclString.trim().isEmpty()) {
					String[] args = aclString.trim().split(";");
					for(String aclStr : args) {
						if(aclStr.matches(".+/.+")) {
							String[] as = aclStr.trim().split("/");
							String keys = as[0].trim();
							if(!keys.matches(".+:.+")) {
								continue;
							}
							String[] up = keys.split(":");
							String username = up[0].trim();
							String password = up[1].trim();
							try {
								keys = DigestAuthenticationProvider.generateDigest(username+":"+password);
							} catch (NoSuchAlgorithmException e1) {
								System.out.println(keys);
							}
							String pString = as[1];
							String[] ps = pString.trim().split(",");
							for(String string : ps) {
								string = string.trim().toUpperCase();
								int perms = Integer.MIN_VALUE;
								if("R".equals(string)||"READ".equals(string)) {
									perms = ZooDefs.Perms.READ;
								}
								if("W".equals(string)||"WRITE".equals(string)) {
									perms = ZooDefs.Perms.WRITE;
								}
								if("C".equals(string)||"CREATE".equals(string)) {
									perms = ZooDefs.Perms.CREATE;
								}
								if("D".equals(string)||"DELETE".equals(string)) {
									perms = ZooDefs.Perms.DELETE;
								}
								if("AD".equals(string)||"ADMIN".equals(string)) {
									perms = ZooDefs.Perms.ADMIN;
								}
								if("A".equals(string)||"ALL".equals(string)) {
									perms = ZooDefs.Perms.ALL;
								}
								if(perms!=Integer.MIN_VALUE) {
									list.add(new ACL(perms, new Id("digest", keys)));
								}
							}
						}
					}
					try{
						zooInspectorManager.alterAcl(selectedNodes, list);
					} catch (Exception e1) {
						JOptionPane.showConfirmDialog(zooInspectorPanel, "没有权限修改节点权限","节点权限修改",JOptionPane.ERROR_MESSAGE);
					}
				}
			}
		});
		tree = new JTree(new DefaultMutableTreeNode());
		tree.setCellRenderer(new ZooInspectorTreeCellRenderer());
		tree.setEditable(false);
		tree.getSelectionModel().addTreeSelectionListener(listener);
		tree.addMouseListener(new MouseAdapter()
		{
			@Override
			public void mouseClicked(MouseEvent e)
			{
				if (e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3)
				{
					// watched, and only show remove if a selected node is being
					// watched
					popupMenu.removeAll();
					popupMenu.add(addNotify);
					popupMenu.add(removeNotify);
					popupMenu.add(addAcl);
					popupMenu.add(alterAcl);
					popupMenu.add(delAcl);
					popupMenu.add(dataLeadingOut);
					popupMenu.add(dataLeadingIn);
					popupMenu.show(ZooInspectorTreeViewer.this, e.getX(), e.getY());
				}
			}
		});
		this.add(tree, BorderLayout.CENTER);
	}

	/**
	 * 
	 */
	public void refreshView()
	{
		final Set<TreePath> expandedNodes = new LinkedHashSet<TreePath>();
		int rowCount = tree.getRowCount();
		for (int i = 0; i < rowCount; i++)
		{
			TreePath path = tree.getPathForRow(i);
			if (tree.isExpanded(path))
			{
				expandedNodes.add(path);
			}
		}
		final TreePath[] selectedNodes = tree.getSelectionPaths();
		SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>()
		{

			@Override
			protected Boolean doInBackground() throws Exception
			{
				tree.setModel(new DefaultTreeModel(new ZooInspectorTreeNode("/", null)));
				return true;
			}

			@Override
			protected void done()
			{
				for (TreePath path : expandedNodes)
				{
					tree.expandPath(path);
				}
				tree.getSelectionModel().setSelectionPaths(selectedNodes);
			}
		};
		worker.execute();
	}

	/**
	 * 
	 */
	public void clearView()
	{
		tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode()));
	}

	/**
	 * @author Colin
	 * 
	 */
	private class ZooInspectorTreeCellRenderer extends DefaultTreeCellRenderer
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = -7514401746917372413L;

		public ZooInspectorTreeCellRenderer()
		{
			setLeafIcon(ZooInspectorIconResources.getTreeLeafIcon());
			setOpenIcon(ZooInspectorIconResources.getTreeOpenIcon());
			setClosedIcon(ZooInspectorIconResources.getTreeClosedIcon());
		}
	}

	/**
	 * @author Colin
	 * 
	 */
	private class ZooInspectorTreeNode implements TreeNode
	{
		private final String nodePath;
		private final String nodeName;
		private final ZooInspectorTreeNode parent;

		public ZooInspectorTreeNode(String nodePath, ZooInspectorTreeNode parent)
		{
			this.parent = parent;
			this.nodePath = nodePath;
			int index = nodePath.lastIndexOf("/");
			if (index == -1)
			{
				throw new IllegalArgumentException("Invalid node path" + nodePath);
			}
			this.nodeName = nodePath.substring(index + 1);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.tree.TreeNode#children()
		 */
		public Enumeration<TreeNode> children()
		{
			try{
				List<String> children = zooInspectorManager.getChildren(this.nodePath);
				Collections.sort(children);
				List<TreeNode> returnChildren = new ArrayList<TreeNode>();
				for (String child : children)
				{
					returnChildren.add(new ZooInspectorTreeNode((this.nodePath.equals("/") ? ""
							: this.nodePath)
							+ "/" + child, this));
				}
				return Collections.enumeration(returnChildren);
			} catch (Exception e) {
				JOptionPane.showConfirmDialog(zooInspectorPanel, "请检查zookeeper是否可用","警告",JOptionPane.ERROR_MESSAGE);
			}
			return null;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.tree.TreeNode#getAllowsChildren()
		 */
		public boolean getAllowsChildren()
		{
			return zooInspectorManager.isAllowsChildren(this.nodePath);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.tree.TreeNode#getChildAt(int)
		 */
		public TreeNode getChildAt(int childIndex)
		{
			String child = zooInspectorManager.getNodeChild(this.nodePath, childIndex);
			if (child != null)
			{
				return new ZooInspectorTreeNode((this.nodePath.equals("/") ? "" : this.nodePath)
						+ "/" + child, this);
			}
			return null;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.tree.TreeNode#getChildCount()
		 */
		public int getChildCount()
		{
			return zooInspectorManager.getNumChildren(this.nodePath);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.tree.TreeNode#getIndex(javax.swing.tree.TreeNode)
		 */
		public int getIndex(TreeNode node)
		{
			return zooInspectorManager.getNodeIndex(this.nodePath);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.tree.TreeNode#getParent()
		 */
		public TreeNode getParent()
		{
			return this.parent;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.tree.TreeNode#isLeaf()
		 */
		public boolean isLeaf()
		{
			return !zooInspectorManager.hasChildren(this.nodePath);
		}

		@Override
		public String toString()
		{
			return this.nodeName;
		}

		@Override
		public int hashCode()
		{
			final int prime = 31;
			int result = 1;
			result = prime * result + getOuterType().hashCode();
			result = prime * result + ((nodePath == null) ? 0 : nodePath.hashCode());
			result = prime * result + ((parent == null) ? 0 : parent.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj)
		{
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			ZooInspectorTreeNode other = (ZooInspectorTreeNode) obj;
			if (!getOuterType().equals(other.getOuterType()))
				return false;
			if (nodePath == null)
			{
				if (other.nodePath != null)
					return false;
			}
			else if (!nodePath.equals(other.nodePath))
				return false;
			if (parent == null)
			{
				if (other.parent != null)
					return false;
			}
			else if (!parent.equals(other.parent))
				return false;
			return true;
		}

		private ZooInspectorTreeViewer getOuterType()
		{
			return ZooInspectorTreeViewer.this;
		}

	}

	/**
	 * @return {@link List} of the currently selected nodes
	 */
	public List<String> getSelectedNodes()
	{
		TreePath[] paths = tree.getSelectionPaths();
		List<String> selectedNodes = new ArrayList<String>();
		if (paths != null)
		{
			for (TreePath path : paths)
			{
				StringBuilder sb = new StringBuilder();
				Object[] pathArray = path.getPath();
				for (Object o : pathArray)
				{
					String nodeName = o.toString();
					if (nodeName.length() > 0)
					{
						sb.append("/");
						sb.append(o.toString());
					}
				}
				selectedNodes.add(sb.toString());
			}
		}
		return selectedNodes;
	}

	public void processEvent(String nodePath, String eventType, Map<String, String> eventInfo)
	{
		StringBuilder sb = new StringBuilder();
		sb.append("节点: ");
		sb.append(nodePath);
		sb.append("\n状态: ");
		sb.append(eventType);
		if (eventInfo != null)
		{
			for (Map.Entry<String, String> entry : eventInfo.entrySet())
			{
				sb.append("\n");
				sb.append(entry.getKey());
				sb.append(": ");
				sb.append(entry.getValue());
			}
		}
		this.toasterManager.showToaster(ZooInspectorIconResources.getInformationIcon(), sb
				.toString());
	}
}
